package main

import (
	"bufio"
	"fmt"
	"io"
	"os"
	"os/exec"

	flag "github.com/spf13/pflag"
)

type selpgArgs struct {
	startPage, endPage, pageLen int
	printDes, inFileName        string
	pageType                    bool
}

func processArgs(args *selpgArgs) {
	argCount := len(os.Args) //至少要有-s和-e两个参数
	if argCount < 3 {
		fmt.Println("not enough arguments")
		os.Exit(1)
	}
	flag.IntVarP(&args.startPage, "startPage", "s", -1, "The start page")
	flag.IntVarP(&args.endPage, "endPage", "e", -1, "The end page")
	flag.IntVarP(&args.pageLen, "pageLen", "l", 72, "The line numbers of each page")
	flag.StringVarP(&args.printDes, "printDes", "d", "", "The name of printer")
	flag.BoolVarP(&args.pageType, "pageType", "f", false, "Defind the end symbol of a page")
	flag.Parse()
	fileName := flag.Args() //非标志参数
	if len(fileName) == 1 {
		args.inFileName = fileName[0] //从文件读入
	} else if len(fileName) == 0 {
		args.inFileName = ""
	} else {
		fmt.Println("Too much arguments")
		os.Exit(1)
	}

	//程序的健壮性
	if args.startPage <= 0 || args.endPage <= 0 {
		fmt.Fprintf(os.Stderr, "The start page and end page should be positive\n")
		os.Exit(1)
	} else if args.startPage > args.endPage {
		fmt.Fprintf(os.Stderr, "The start page should less than end page")
		os.Exit(1)
	} else if args.startPage < 0 {
		fmt.Fprintf(os.Stderr, "The start page can't be less than 0")
		os.Exit(1)
	} else if args.pageType == true && args.pageLen != 72 {
		fmt.Fprintf(os.Stderr, "-l and -f can't be used together")
		os.Exit(1)
	} else if args.pageLen <= 0 {
		fmt.Fprintf(os.Stderr, "The line of each page can't be less than 1")
		os.Exit(1)
	} else {//单元测试时将其注释
		pageType := "decided by page length"
		if args.pageType {
			pageType = "decided by the end sign '/f'"
		}
		fmt.Printf("start page: %d\nend page: %d\npage length: %d\npage type: %s\ninput file: %s\nprint destination: %s\n\n", args.startPage, args.endPage, args.pageLen, pageType, args.inFileName, args.printDes)
	}
}

func processInput(args *selpgArgs) {
	var filein *os.File
	var err error
	if args.inFileName != "" {
		filein, err = os.Open(args.inFileName) //用于读取访问
		defer filein.Close()
		if err != nil {
			fmt.Fprintf(os.Stderr, "Failed to open the file %s\n%s", args.inFileName, err)
			os.Exit(2)
		}
	} else {
		filein = os.Stdin
	}

	if args.printDes == "" {
		output(os.Stdout, filein, args)
	} else {
		command := exec.Command("lp", "-d"+args.printDes)
		outFile, err := command.StdinPipe()
		if err != nil {
			fmt.Fprintf(os.Stderr, "Failed to open pipe\n")
			os.Exit(2)
		}
		output(outFile, filein, args)
	}
}

func output(out interface{}, in *os.File, args *selpgArgs) {
	var pageNum int
	if args.pageType {
		pageNum = 0
	} else {
		pageNum = 1
	}

	lineNum := 0
	buffer := bufio.NewReader(in)
	for {
		var pageBuf string
		var err error

		if args.pageType {
			pageBuf, err = buffer.ReadString('\f')
			pageNum++
		} else {
			pageBuf, err = buffer.ReadString('\n')
			lineNum++
			if lineNum > args.pageLen {
				pageNum++
				lineNum = 1
			}
		}

		if err != nil && err != io.EOF {
			fmt.Fprintf(os.Stderr, "errors in reading file!\n")
			os.Exit(3)
		}

		if pageNum >= args.startPage && pageNum <= args.endPage {
			if len(args.printDes) == 0 {
				printOut, ok := out.(*os.File)
				if ok {
					fmt.Fprintf(printOut, "%s", pageBuf)
				} else {
					fmt.Fprintf(os.Stderr, "Wrong printing type!\n")
					os.Exit(3)
				}
			} else {
				printOut, ok := out.(io.WriteCloser)
				if ok {
					printOut.Write(([]byte)(pageBuf))
				} else {
					fmt.Fprintf(os.Stderr, "Wrong printing type!\n")
					os.Exit(3)
				}
			}
		}

		if err == io.EOF {
			break
		}
	}

	if pageNum < args.startPage {
		fmt.Fprintf(os.Stderr, "The start page bigger than total pages %d, no output written", pageNum)
	} else if pageNum < args.endPage {
		fmt.Fprintf(os.Stderr, "The end page bigger than total pages %d, output is not enough\n", pageNum)
		os.Exit(4)
	}
}

func main() {
	cmd := os.Args[0]//单元测试时将其注释
	fmt.Printf("program name: %s\n", cmd)//单元测试时将其注释
	var args selpgArgs
	processArgs(&args)
	processInput(&args)
}